<?php

error_reporting(E_ALL);
ini_set('display_errors', true);

/**
 * 淘宝API接口(支持TOP及OAuth2认证协议)
 *
 * @author: cjj
 */
class TaobaoClient {

    public $protocol = 'oauth2'; //top,oauth2
    public $environment = 'taobao'; //正式环境:taobao,沙箱环境sandbox
    public $readTimeout, $connectTimeout;
    public $gatewayUrl = 'http://121.43.116.17:30003';
    public $format = 'json'; //json & xml
    public $checkRequest = true;
    protected $apiUrl = array();
    protected $signMethod = 'md5';
    protected $apiVersion = '2.0';
    protected $sdkVersion = 'top-sdk-php-20150118';
    private $_token;
    private $_urlMap = array(//认证及TOKEN获取网址列表
        'top_authorize' => 'http://container.api.#ENVIRONMENT#.com/container?encode=utf-8&appkey=',
        'top_token' => '',
        'oauth2_authorize' => 'https://oauth.#ENVIRONMENT#.com/authorize',
        'oauth2_token' => 'https://oauth.#ENVIRONMENT#.com/token',
    );
    private $_authorize_token_model, $_queue;

    public function __construct($options = array()) {
        $token = isset($options[0]) ? $options[0] : null;
        $is_cli = isset($options[1]) ? $options[1] : false;
        $this->_authorize_token_model = isset($options[2]) ? $options[2] : null;
        $this->_queue = isset($options[3]) ? $options[3] : null;
        if (!$token) {
            if (!$is_cli) {
                if (isset($_REQUEST['error'])) {
                    if ('invalid_client' === $_REQUEST['error']) {
                        throw new APIException(10001, '您还未得到花集通授权，暂时还不能使用！', $_REQUEST);
                    } else {
                        throw new APIException(10001, $_REQUEST['error_description'], $_REQUEST);
                    }
                }
                if (!isset($_REQUEST['code']) && !isset($_REQUEST['top_session'])) {
                    $this->authorize();
                    exit();
                }
            }
        } else {
            $sessionKey = isset($token->access_token) ? $token->access_token : null;
            if (!$sessionKey) {
                throw new APIException(10002, '淘宝接口 SessionKey 获取失败');
            }
            $this->_token = $token;
        }
    }

    /**
     * 用户认证
     */
    public function authorize() {
        $urlType = strtolower($this->protocol . '_authorize');
        $authorizeUrlMap = isset($this->_urlMap[$urlType]) ? $this->_urlMap[$urlType] : '';
        if (!$authorizeUrlMap) {
            throw new APIException(10003, '淘宝数据读取异常，未发现认证网址');
        }
        $authorizeUrl = str_replace('#ENVIRONMENT#', $this->environment, $authorizeUrlMap);
        if ($this->protocol == 'top') {
            $ret_url = $authorizeUrl . TB_APP_ID;
        } else {
            $authorizeUrl .= '?';
            $state = time();
            $_SESSION["tb_state"] = $state;
            $params = array(
                "response_type" => "code",
                "client_id" => TB_APP_ID,
                "redirect_uri" => TB_APP_CALLBACK,
                "state" => $state
            );
            $get = array();
            foreach ($params as $key => $val) {
                $get[] = $key . '=' . urlencode($val);
            }
            $ret_url = $authorizeUrl . join('&', $get);
        }
        $back_url = empty($_GET['callback']) ? "/index.php" : $_GET['callback'];
        $_SESSION['back_url'] = $back_url;
        header('location:' . $ret_url);
        exit();
    }

    /**
     * 获取AccessToken
     *
     * @return array|mixed
     * @throws Exception
     */
    public function getToken() {
        if ($this->_token && is_array($this->_token) && isset($this->_token['access_token'])) {
            return $this->_token;
        }
        if (!isset($_GET['code']) && !isset($_REQUEST['top_session'])) {
            $this->authorize();
            return null;
        }
        if (isset($_REQUEST['top_session'])) {//TOP协议认证
            $top_parameters = array();
            parse_str(base64_decode($_REQUEST['top_parameters']), $top_parameters);
            $visitor_id = $top_parameters['visitor_id'];
            $visitor_nick = $top_parameters['visitor_nick'];
            unset($top_parameters['visitor_id'], $top_parameters['visitor_nick']);
            $top_parameters['taobao_user_id'] = $visitor_id;
            $top_parameters['taobao_user_nick'] = $visitor_nick;
            $token = (object) $top_parameters;
            $token->access_token = $_REQUEST['top_session'];
            return $token;
        }
        $urlType = strtolower($this->protocol . '_token');
        $taobaoUrlMap = isset($this->_urlMap[$urlType]) ? $this->_urlMap[$urlType] : '';
        if (!$taobaoUrlMap) {
            throw new APIException(10004, '淘宝数据读取异常，未发现Token网址');
        }
        $tokenUrl = str_replace('#ENVIRONMENT#', $this->environment, $taobaoUrlMap);
        //oauth2协议认证
        $code = $_GET['code'];
        $post_fields = array(
            'grant_type' => "authorization_code",
            'client_id' => TB_APP_ID,
            'client_secret' => TB_APP_KEY,
            'code' => $code,
            'redirect_uri' => TB_APP_CALLBACK
        );
        return json_decode($this->send($tokenUrl, $post_fields));
    }

    /**
     * 得到api接口数据
     *
     * @param string $method
     * @param array $paras
     *
     * @return mixed
     * @throws Exception
     */
    public function api($method, $paras) {
        $access_token = isset($this->_token) && $this->_token ? $this->_token->access_token : '';
        if (!$access_token) {
            throw new APIException(20002, 'Access Token不存在[' . $method . print_r($paras, true) . ']', $this->_token);
        }
        $paramArr = array(
            'method' => $method,
            'session' => $access_token,
            'timestamp' => date('Y-m-d H:i:s'),
            'format' => $this->format,
            'app_key' => TB_APP_ID,
            'v' => '2.0',
            'sign_method' => 'md5'
        );
        if ($paras != "") {
            $paramArr += $paras;
        }
        $sign = $this->createSign($paramArr, TB_APP_KEY);
        $strParam = $this->createStrParam($paramArr);
        $strParam .= 'sign=' . $sign;
        $urlStr = $this->gatewayUrl . '?' . $strParam;
        $result = $this->send($urlStr);
        if ($this->format === 'xml') {
            $result = $this->_xmlToArray($result);
        } else {
            $result = my_json_decode($result, true);
        }
        if (isset($result['error_response'])) {
            $error = $result['error_response'];
            $error_code = isset($error['code']) ? $error['code'] : 0;
            if ($this->_authorize_token_model && $this->_token && isset($this->_token->taobao_user_id) && $error_code != 15) {
                $this->_authorize_token_model->updateToken($this->_token->taobao_user_id, array('is_error_stop' => 1));
            }
            $sub_code = isset($error['sub_code']) ? $error['sub_code'] : '';
            if (isset($error['code']) && $error['msg']) {
                $fixed_msg = "\n";
                switch ($error['code']) {
                    case '3':
                        $fixed_msg = '(图片上传失败)';
                        break;
                    case '7':
                        $fixed_msg.='(应用调用次数或频率超限)';
                        break;
                    case '9':
                        $fixed_msg = '(HTTP方法被禁止)';
                        break;
                    case '10':
                        $fixed_msg = '(服务不可用)';
                        break;
                    case '11':
                        $fixed_msg = '(开发者权限不足)';
                        break;
                    case '12':
                        $fixed_msg = '(用户权限不足)';
                        break;
                    case '15':
                        $fixed_msg = '(远程同步错误)';
                        break;
                    default:
                        $fixed_msg.='(' . $error['msg'] . '[' . $error['code'] . '])';
                }
                if ($error['code'] == '15' && $sub_code == 'isp.remote-service-timeout') {//15超时 重新写入队列
                    $tid = isset($paras['tid']) ? $paras['tid'] : 0;
                    if ($tid && $this->_queue) {
                        $this->_queue->push($tid);
                    }
                } else {
                    throw new APIException(20001, "淘宝同步异常" . $fixed_msg . '!', $error);
                }
            } else {
                throw new APIException(20000, "淘宝数据同步异常!", $error);
            }
        }
        return $result;
    }

    /**
     * 淘宝参数签名
     *
     * @param $paramArr
     *
     * @return string
     */
    public function createSign($paramArr) {
        $sign = TB_APP_KEY;
        ksort($paramArr);
        foreach ($paramArr as $key => $val) {
            if ($key != '' && $val != '') {
                $sign .= $key . $val;
            }
        }
        $sign = strtoupper(md5($sign . TB_APP_KEY));
        return $sign;
    }

    /**
     * 淘宝参数组合
     *
     * @param $paramArr
     *
     * @return string
     */
    public function createStrParam($paramArr) {
        $strParam = '';
        foreach ($paramArr as $key => $val) {
            if ($key != '' && $val != '') {
                $strParam .= $key . '=' . urlencode($val) . '&'; //(mb_convert_encoding( ,"GBK"
            }
        }
        return $strParam;
    }

    /**
     * 发送请求
     *
     * @param      $url
     * @param null $postFields
     *
     * @return mixed
     * @throws Exception
     */
    public function send($url, $postFields = null) {
        if ($this->protocol == 'top') {
            return file_get_contents($url);
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_FAILONERROR, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if ($this->readTimeout) {
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
        }
        if ($this->connectTimeout) {
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
        }
        //https 请求
        if (strlen($url) > 5 && strtolower(substr($url, 0, 5)) == "https") {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }

        if (is_array($postFields) && 0 < count($postFields)) {
            $postBodyString = "";
            $postMultipart = false;
            foreach ($postFields as $k => $v) {
                if ("@" != substr($v, 0, 1)) {//判断是不是文件上传
                    $postBodyString .= "$k=" . urlencode($v) . "&";
                } else {//文件上传用multipart/form-data，否则用www-form-urlencoded
                    $postMultipart = true;
                }
            }
            unset($k, $v);
            curl_setopt($ch, CURLOPT_POST, true);
            if ($postMultipart) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
            } else {
                curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1));
            }
        }
        $response = curl_exec($ch);
        if (curl_errno($ch)) {
            throw new APIException(1001, "API调用异常\n" . $url . "\n" . curl_error($ch));
        } else {
            $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if (200 !== $httpStatusCode) {
                throw new APIException($httpStatusCode, $response);
            }
        }
        curl_close($ch);
        return $response;
    }

    /**
     * XML转成数组
     *
     * @param $xml
     *
     * @return array
     */
    private function _xmlToArray($xml) {
        $reg = "/<(\\w+)[^>]*?>([\\x00-\\xFF]*?)<\\/\\1>/";
        if (!preg_match_all($reg, $xml, $matches)) {
            return $xml;
        }
        $count = count($matches[0]);
        $arr = array();
        for ($i = 0; $i < $count; $i++) {
            $key = $matches[1][$i];
            $val = $this->_xmlToArray($matches[2][$i]); // 递归
            if (array_key_exists($key, $arr)) {
                if (is_array($arr[$key])) {
                    if (!array_key_exists(0, $arr[$key])) {
                        $arr[$key] = array($arr[$key]);
                    }
                } else {
                    $arr[$key] = array($arr[$key]);
                }
                $arr[$key][] = $val;
            } else {
                $arr[$key] = $val;
            }
        }
        return $arr;
    }

}
